package com.example.chuzhou.sm2;


import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.junit.platform.commons.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class SM2EncDecUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(SM2EncDecUtils.class);
    private static final String publicKeyName = "PUBLICKEY";
    private static final String privateKeyName = "PRIVATEKEY";

    @Resource
    private static RedisTemplate redisTemplate;

    static {
//        redisTemplate = (RedisTemplate) SpringUtils.getBean("redisTemplate");
    }

    private SM2EncDecUtils() {
    }


    /**
     * 获取公钥字符串数据
     *
     * @return
     * @throws Exception
     */
    public static String getPublicKeys() throws Exception {
        if (isCreateKeyPair()) {
            String pubKeyStr = redisTemplate.opsForValue().get(publicKeyName).toString();
            return pubKeyStr.trim();
        } else {
            return generateKeyPair().get(publicKeyName).trim();
        }
    }


    /**
     * 判断key是否存在且有效
     *
     * @return
     */
    private static boolean isCreateKeyPair() {
        boolean createKeyPair = false;
        if (redisTemplate.hasKey(publicKeyName) && redisTemplate.hasKey(privateKeyName)) {
            createKeyPair = true;
        }
        return createKeyPair;
    }

    /**
     * 保存秘钥
     */
    private static void saveKeyPair(String pubKey, String priKey) {
        try {

            redisTemplate.opsForValue().set(publicKeyName, pubKey, 24 * 60 * 60, TimeUnit.SECONDS);
            redisTemplate.opsForValue().set(privateKeyName, priKey, 24 * 60 * 60, TimeUnit.SECONDS);
//            LOGGER.info("【秘钥缓存成功,24小时内有效！】");
        } catch (Exception var5) {
            var5.printStackTrace();
        }

    }


    /**
     * 获取私钥
     *
     * @return
     */
    public static String getPrivateKey() {
        if (isCreateKeyPair()) {
            String priKeyStr = redisTemplate.opsForValue().get(privateKeyName).toString();
            return priKeyStr.trim();
        } else {
            return generateKeyPair().get(privateKeyName).trim();
        }
    }


    public static String decryptStringByJs(String encrypttext) {
        String text = decryptString(encrypttext);
        return text == null ? null : text;
    }

    public static String decryptString(String encrypttext) {
        if (StringUtils.isBlank(encrypttext)) {
            return null;
        } else {
            try {
                String privateKey = getPrivateKey();
                System.out.println("--------------privateKey  " + privateKey);
                String plainTextEncripted = new String(SM2EncDecUtils.decrypt(Util.hexToByte(privateKey), Util.hexToByte(encrypttext)), "UTF-8");
                return plainTextEncripted;
            } catch (NullPointerException var3) {
                LOGGER.error("keyPair cannot be null.");
            } catch (Exception var4) {
            }

            return null;
        }
    }


    // 生成随机秘钥对
    public static Map<String, String> generateKeyPair() {
        SM2 sm2 = SM2.Instance();
        AsymmetricCipherKeyPair key = null;
        while (true) {
            key = sm2.ecc_key_pair_generator.generateKeyPair();
            if (((ECPrivateKeyParameters) key.getPrivate()).getD().toByteArray().length == 32) {
                break;
            }
        }
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();
        String pubk = Util.byteToHex(publicKey.getEncoded());
        String prik = Util.byteToHex(privateKey.toByteArray());
        System.out.println("公钥: " + pubk);
        System.out.println("私钥: " + prik);


        saveKeyPair(pubk, prik);


        Map<String, String> result = new HashMap<>();

        result.put(publicKeyName, pubk);
        result.put(privateKeyName, prik);

        return result;
    }

    // 数据加密
    public static String encrypt(byte[] publicKey, byte[] data) throws IOException {
        if (publicKey == null || publicKey.length == 0) {
            return null;
        }

        if (data == null || data.length == 0) {
            return null;
        }

        byte[] source = new byte[data.length];
        System.arraycopy(data, 0, source, 0, data.length);
        Cipher cipher = new Cipher();
        SM2 sm2 = SM2.Instance();
        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);
        ECPoint c1 = cipher.Init_enc(sm2, userKey);
        cipher.Encrypt(source);
        byte[] c3 = new byte[32];
        cipher.Dofinal(c3);
        return new StringBuffer(Util.byteToHex(c1.getEncoded())).append(Util.byteToHex(c3)).append(Util.byteToHex(source)).toString();
    }

    // 数据解密
    public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException {
        if (privateKey == null || privateKey.length == 0) {
            return null;
        }

        if (encryptedData == null || encryptedData.length == 0) {
            return null;
        }
        // 加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2
        String data = Util.byteToHex(encryptedData);

        byte[] c1Bytes = Util.hexToByte(data.substring(0, 130));
        int c2Len = encryptedData.length - 97;
        byte[] c3 = Util.hexToByte(data.substring(130, 130 + 64));
        byte[] c2 = Util.hexToByte(data.substring(194, 194 + 2 * c2Len));

        SM2 sm2 = SM2.Instance();
        BigInteger userD = new BigInteger(1, privateKey);

        // 通过C1实体字节来生成ECPoint
        ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);
        Cipher cipher = new Cipher();
        cipher.Init_dec(userD, c1);
        cipher.Decrypt(c2);
        cipher.Dofinal(c3);

        // 返回解密结果
        return c2;
    }

    public static void main(String[] args) throws Exception {

        singleThreadTest();
//        mutiThreadTest();
    }

    private static void singleThreadTest() throws Exception {
        String plainText = "hello fhm";
        byte[] sourceData = plainText.getBytes();
//        Map<String, String> keymap = generateKeyPair();


       /* String cipherText = SM2EncDecUtils.encrypt(Util.hexToByte(keymap.get(publicKeyName)), sourceData);
        System.out.println("加密前长度: " + plainText.length() + ";加密后长度: " + cipherText.length());
        String plainTextEncripted = new String(SM2EncDecUtils.decrypt(Util.hexToByte(keymap.get(privateKeyName)), Util.hexToByte(cipherText)));
       */
        String pubKey = "041eab0797f1fc3a88ee89defbf18bceaeb2d890e257d08e2417dd9a78e363be0778516807a6fcc0016f985094b3d580038fac11819edbb36f03091d5a0feb50f2";
        String priKey = "fba2dc188e2c1765ca7081a987af2cac92cce640ff46b06a7929e1a277d2ab52";


//        String cipherText = SM2EncDecUtils.encrypt(Util.hexToByte(pubKey), sourceData);
//        System.out.println("cipherText = " + cipherText);
//        System.out.println("ciphString长度: " + plainText.length() + ";加密后长度: " + cipherText.length());

//        cipherText = "04570021572715032FD61C069EBBD20A332D12556EB9437729DEA2A047D2FDEDD214B5AED8E5C356A63BF73D268E34AFA9EF80B8B74E8C1619BFA7A1F6F13E7550A3B519D008BEFE34CB58A3B526727B2472ECB28EDBC5767E2268977178AE623A54A763F4BAC440403B";
        String cipherText = "04C2910BD2A52B3171FCCC8DD93BC59A779F3BFC726CD39F51E6C913087F43AC1FA36CF38160331C0DFB1E43897D61CFAE9F6BDD3C6D3F6851E2B17868D4DA1102AE2D6657EA7C1609D4268D8A2A1200E85C4D121F17214393B547DF2A4D3C2D5B9FDBE1CBEF255B7F55";

        System.out.println(new String(Objects.requireNonNull(SM2EncDecUtils.decrypt(Util.hexToByte(priKey), Util.hexToByte(cipherText))), StandardCharsets.UTF_8));
    }

    private static void mutiThreadTest() {
        String plainText = "sourceText";
        byte[] sourceData = plainText.getBytes();

        Map<String, String> keymap = generateKeyPair();
        int counts = 10;
        for (int i = 0; i < counts; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < counts; j++) {
                        String cipherText = SM2EncDecUtils.encrypt(Util.hexToByte(keymap.get(publicKeyName)), sourceData);
                        if (!plainText.equals(new String(SM2EncDecUtils.decrypt(Util.hexToByte(keymap.get(privateKeyName)), Util.hexToByte(cipherText))))) {
                            System.out.println("------解密后同原文不一致:" + Thread.currentThread().getName() + "--------------");
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println(" --------------->线程" + Thread.currentThread().getName() + "执行完成.---------------------");
            }
            ).start();
        }
    }
}
